昨天講完 vars 跟 secret 怎麼分,結尾說今天要回到存筆記這條主線,講 Jina Reader 怎麼把一個網址變成乾淨的內文,餵給 AI 分類。
先看 fetchReadable 這個函式,line-notion-bot 抓網頁內容全靠它:
async function fetchReadable(url, env) {
// Jina Reader:免費把任意網頁轉成乾淨純文字
// 帶上 API Key 才有專屬額度(100 次/分鐘),否則跟全世界共用 Workers 出口 IP 的 20 次/分鐘額度,很容易被打光
const headers = { 'User-Agent': 'line-notion-bot' };
if (env.JINA_API_KEY) {
headers['Authorization'] = `Bearer ${env.JINA_API_KEY}`;
}
const res = await fetch(`https://r.jina.ai/${url}`, { headers });
if (!res.ok) throw new Error(`無法讀取網頁(${res.status})`);
const text = await res.text();
return text.slice(0, MAX_CONTENT_CHARS);
}
重點在 https://r.jina.ai/${url} 這行,Jina Reader 的用法就是把想抓的網址直接接在 r.jina.ai/ 後面當路徑,丟過去它就把那個網頁轉成乾淨的純文字回傳,不用自己寫 HTML parser,也不用處理什麼奇怪的排版標籤。對一支只有一個檔案的 bot 來說,這樣換來的簡單值回票價:多寫一個網頁解析器要處理的邊角案例只會更多,不會更少。
JINA_API_KEY 有沒有設定,走的是完全不同的額度:帶 Key 是專屬的每分鐘 100 次,沒帶就是跟全世界共用 Cloudflare Workers 出口 IP 的每分鐘 20 次,Day 4 已經講過這個取捨,這裡只是實際看到程式怎麼寫——if (env.JINA_API_KEY) 判斷式成立才加 Authorization header,不成立就維持沒有這個 header 的原始請求,Jina 那邊自然會把它當成沒帶 Key 的訪客流量處理。這種「選填的環境變數沒設定就退回預設行為」的寫法,跟 Day 7 提過的 OWNER_USER_ID 是同一套邏輯,會一直在這支 bot 裡重複出現。
再來是今天標題後半段,MAX_CONTENT_CHARS。這個常數定義在檔案最上面的可自訂區,跟 CATEGORIES、PROP 放在一起:
// 丟給 AI 的內文長度上限(避免 token 爆掉)
const MAX_CONTENT_CHARS = 6000;
fetchReadable 最後一行 text.slice(0, MAX_CONTENT_CHARS),做的事情很直白:不管 Jina 抓回來的內文有多長,一律從第一個字切到第 6000 個字,後面全部丟掉。這裡故意不做任何「找到完整句子再切」之類的智慧判斷,切在一個字的中間也沒關係——反正丟給 AI 的只是要拿來分類跟寫摘要,AI 不需要看到全文,看到內文的前半段大致就能判斷這篇在講什麼、該歸哪一類。要是切爛了最後一句話,頂多摘要少了一點細節,不影響分類跟標籤的準確度,比起為了保留完整句子多寫一段判斷邏輯,這個代價我覺得划算。
6000 字這個數字本身也不是抓好看的整數,是抓 token 預算來的:中文一個字大概對應 1–2 個 token,6000 字換算下來大概是免費模型 context window 裡不會出問題的安全值,留夠空間給 system prompt、既有標籤清單、還有 AI 回傳的 JSON。真的餵進去比較長的文章,也不會因為 token 爆掉整個請求直接失敗。
還有一個地方要提一下:fetchReadable 拋出的錯誤,在 handleTextMessage 那邊是被接住的:
try {
content = await fetchReadable(url, env);
} catch (fetchErr) {
content = '(此網頁無法自動讀取內文,可能為需要登入的社群平台)';
}
Facebook、Instagram 這類擋爬蟲的平台,Jina Reader 常常抓不到東西,res.ok 是 false 就會丟錯誤,但這裡選擇接住它、換一句提示文字繼續往下走,而不是讓整個流程失敗。這個「抓不到內文也要想辦法存下來」的設計,之後 Day 13 會整天講這件事。
Jina Reader 怎麼把網址轉成文字、6000 字這個上限怎麼來的,今天算是講完了。明天進到分類 Prompt 本身:固定分類清單、2–4 個標籤、2–3 句摘要,這些規則怎麼寫進 system prompt 裡。